package io.gitee.falllee.dao.util;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.Blob;
import java.sql.SQLException;
import java.sql.Time;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONValidator;
import com.alibaba.fastjson.TypeReference;

import io.gitee.falllee.common.util.LogUtils;
import io.gitee.falllee.dao.annotation.Column;
import io.gitee.falllee.dao.annotation.Table;
import io.gitee.falllee.dao.enums.DbTypeEnum;
import io.gitee.falllee.dao.enums.JdbcTypeEnum;
import io.gitee.falllee.dao.model.BaseDaoConfig;
import io.gitee.falllee.dao.model.DaoConst;
import io.gitee.falllee.dao.model.DaoException;

/**
 * @author batty
 *
 */
public class ConvertUtils {

    private static final Map<String, Method> getMethodMap = new ConcurrentHashMap<>();

    private static final Map<String, Method> setMethodMap = new ConcurrentHashMap<>();
    
    private static final Map<String, String> underline2HumpMap = new ConcurrentHashMap<>();
    
    private static final Map<String, String> hump2UnderlineMap = new ConcurrentHashMap<>();
    
    private static final Pattern UNDERLINE_PATTERN = Pattern.compile("_(\\w)");

    private static final Pattern HUMP_PATTERN = Pattern.compile("([A-Z])");

    private ConvertUtils() {
    }

    private static String getColumnValue(Column col) {
        if (col == null) {
            return "";
        }
        String val = col.value();
        BaseDaoConfig daoConfig = CommonUtils.getDaoConfig();
        if (daoConfig == null) {
            return val;
        }
        return daoConfig.getValue(val);
    }

    /**
     * 实体转Map
     * 
     * @param t
     * @return
     */
    public static <T> Map<String, Object> entity2Map(T t) {
        Map<String, Object> map = new HashMap<>(16);
        Class<? extends Object> clazz = t.getClass();
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            Column col = method.getAnnotation(Column.class);
            String columnName = getColumnValue(col);
            if (!StringUtils.hasLength(columnName)) {
                continue;
            }
            try {
                map.put(columnName, method.invoke(t));
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                Map<String, Object> paramMap = new LinkedHashMap<>();
                paramMap.put("t", t);
                LogUtils.logException(paramMap, e);
            }
        }
        return map;
    }

    /**
     * Map转实体
     * 
     * @param map
     * @param clazz
     * @return
     */
    public static <T> T map2Entity(Map<String, Object> map, Class<T> clazz) {
        if (map == null || map.isEmpty()) {
            return null;
        }
        try {
            T t = clazz.getDeclaredConstructor().newInstance();
            Table table = getTable(clazz);
            String tableName = table.value();
            Iterator<Entry<String, Object>> iterator = map.entrySet().iterator();
            while (iterator.hasNext()) {
                Entry<String, Object> entry = iterator.next();
                String columnName = entry.getKey();
                Object value = entry.getValue();
                String key = String.format("%s.%s", tableName, columnName);
                Method setMethod = getSetMethod(key, clazz);
                Method getMethod = getGetMethod(key, clazz);
                setEntityValue(map, t, value, setMethod, getMethod, clazz);
            }
            return t;
        } catch (Exception e) {
            Map<String, Object> paramMap = new LinkedHashMap<>();
            paramMap.put("map", map);
            paramMap.put("clazz", clazz);
            LogUtils.logException(paramMap, e);
            return null;
        }
    }

    private static <T> void setEntityValue(Map<String, Object> map, T t, Object value, Method setMethod,
        Method getMethod, Class<T> clazz) {
        if (value != null && setMethod != null && getMethod != null) {
            try {
                Object val = null;
                Class<?> rt = getMethod.getReturnType();
                if (DaoConst.BYTE_ARRAY.equals(rt.getSimpleName())) {
                    if (value instanceof Blob) {
                        val = BlobToBytes((Blob) value);
                    } else {
                        val = value;
                    }
                } else {
                    val = string2Object(value.toString(), rt);
                }
                setMethod.invoke(t, val);
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                Map<String, Object> paramMap = new LinkedHashMap<>();
                paramMap.put("map", map);
                paramMap.put("value", value);
                paramMap.put("setMethod", setMethod != null ? setMethod.getName() : "");
                paramMap.put("getMethod", getMethod != null ? getMethod.getName() : "");
                paramMap.put("clazz", clazz);
                LogUtils.logException(paramMap, e);
            }
        }
    }

    private static <T> Method getSetMethod(String key, Class<T> clazz) {
        return setMethodMap.computeIfAbsent(key, p -> {
            Method getMethod = getGetMethod(p, clazz);
            if (getMethod != null) {
                String getMethodName = getMethod.getName();
                String setMethodName = getMethodName.replaceFirst("get", "set");
                try {
                    return clazz.getMethod(setMethodName, getMethod.getReturnType());
                } catch (NoSuchMethodException | SecurityException e) {
                    LogUtils.logException(e);
                }
            }
            return null;
        });
    }

    private static <T> Method getGetMethod(String key, Class<T> clazz) {
        Table table = getTable(clazz);
        String tableName = table.value();
        return getMethodMap.computeIfAbsent(key, p -> {
            for (Method method : clazz.getMethods()) {
                Column col = method.getAnnotation(Column.class);
                String columnName = getColumnValue(col);
                if (StringUtils.hasLength(columnName)) {
                    String getKey = String.format("%s.%s", tableName, columnName);
                    if (getKey.equals(p)) {
                        return method;
                    }
                }
            }
            return null;
        });
    }

    /**
     * Map集合转实体集合
     * 
     * @param mapList Map集合
     * @param clazz   实体类
     * @return 实体集合
     */
    public static <T> List<T> mapList2EntityList(List<Map<String, Object>> mapList, Class<T> clazz) {
        List<T> list = new ArrayList<>();
        for (Map<String, Object> map : mapList) {
            T t = map2Entity(map, clazz);
            if (t != null) {
                list.add(t);
            }
        }
        return list;
    }

    public static <T> List<Map<String, Object>> entityList2MapList(List<T> list) {
        List<Map<String, Object>> mapList = new ArrayList<>();
        for (T t : list) {
            Map<String, Object> map = entity2Map(t);
            if (!ObjectUtils.isEmpty(map)) {
                mapList.add(map);
            }
        }
        return mapList;
    }

    /**
     * 字符串转对象
     * 
     * @param str
     * @param clazz
     * @return
     */
    public static Object string2Object(String str, Class<?> clazz) {
        if (Number.class.equals(clazz.getSuperclass())) {
            if (Boolean.TRUE.toString().equals(str)) {
                return 1;
            } else if (Boolean.FALSE.toString().equals(str)) {
                return 0;
            }
        }
        String className = clazz.getSimpleName();
        switch (className) {
        case "byte":
        case "Byte":
            return Byte.valueOf(str);
        case "short":
        case "Short":
            return Short.valueOf(str);
        case "int":
        case "Integer":
            return Integer.valueOf(str);
        case "long":
        case "BigInteger":
        case "Long":
            return Long.valueOf(str);
        case "BigDecimal":
            return new BigDecimal(str);
        case "Time":
            return Time.valueOf(str);
        case "Date":
            return string2Date(str);
        case "boolean":
        case "Boolean":
            return Boolean.valueOf(str) || DaoConst.ONE.equals(str);
        case "float":
        case "Float":
            return Float.valueOf(str);
        case "double":
        case "Double":
            return Double.valueOf(str);
        default:
            return str;
        }
    }

    /**
     * Java类型转JDBC类型
     * 
     * @param clazz Java类型
     * @return JDBC类型
     * @throws Exception 异常
     */
    public static String java2JdbcType(Class<?> clazz) {
        switch (clazz.getSimpleName()) {
        case "byte":
        case "Byte":
            return JdbcTypeEnum.TINYINT.getJdbcType();
        case "short":
        case "Short":
            return JdbcTypeEnum.SMALLINT.getJdbcType();
        case "int":
        case "Integer":
            return JdbcTypeEnum.INTEGER.getJdbcType();
        case "long":
        case "BigInteger":
        case "Long":
            return JdbcTypeEnum.BIGINT.getJdbcType();
        case "BigDecimal":
            return JdbcTypeEnum.DECIMAL.getJdbcType();
        case "Date":
            return JdbcTypeEnum.TIMESTAMP.getJdbcType();
        case "boolean":
        case "Boolean":
            return JdbcTypeEnum.BIT.getJdbcType();
        case "float":
        case "Float":
            return JdbcTypeEnum.FLOAT.getJdbcType();
        case "double":
        case "Double":
            return JdbcTypeEnum.DOUBLE.getJdbcType();
        default:
            return JdbcTypeEnum.VARCHAR.getJdbcType();
        }
    }
    
    public static boolean checkTimestamp(String str) {
        String timestampReg = "[0-9]{13}";
        return Pattern.matches(timestampReg, str);
    }

    /**
     * 字符串转日期
     * 
     * @param str
     * @return
     */
    public static Date string2Date(String str) {
        // 判断时间戳
        if (checkTimestamp(str)) {
            return new Date(Long.valueOf(str));
        }
        // 处理秒丢失的字符串
        String timeReg = ".*[^:](([0-2][0-3])|([0-1][0-9])):[0-5][0-9]";
        if (Pattern.matches(timeReg, str)) {
            str = str.concat(":00");
        }
        String z = "Z";
        String t = "T";
        Integer dayLength = 10;
        Integer dtLength = 19;
        SimpleDateFormat format;
        Integer length = str.length();
        if (str.indexOf(z) >= 0) {
            str = str.replace("Z", " UTC");
            format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS Z");
        } else if (str.indexOf(t) >= 0) {
            format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
        } else if (length > dtLength) {
            format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        } else if (length > dayLength) {
            format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        } else {
            format = new SimpleDateFormat("yyyy-MM-dd");
        }
        try {
            return format.parse(str);
        } catch (ParseException e) {
            Map<String, Object> paramMap = new LinkedHashMap<>();
            paramMap.put("str", str);
            LogUtils.logException(paramMap, e);
            return null;
        }
    }

    /**
     * 把日期转换为只包含年月日的日期。
     * 
     * @param date
     * @return
     * @throws ParseException
     * @throws Exception
     */
    public static Date toYMDDate(Date date) throws ParseException {
        if (date == null) {
            return date;
        }
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        return sdf.parse(sdf.format(date));
    }

    /**
     * 百分比计算公式
     * 
     * @param num   分子
     * @param total 分母
     * @param scale 精度
     * @return 返回最后的百分比字符串
     */
    public static String accuracy(double num, double total, int scale) {
        DecimalFormat df = (DecimalFormat) NumberFormat.getInstance();
        // 可以设置精确几位小数
        df.setMaximumFractionDigits(scale);
        // 模式 例如四舍五入
        df.setRoundingMode(RoundingMode.HALF_UP);
        double accuracyNum = num / total * 100;
        return df.format(accuracyNum) + "%";
    }

    /**
     * 字符串转长整形
     * 
     * @param str
     * @param defaultVal
     * @return
     */
    public static Long string2Long(String str, Long defaultVal) {
        Long num = defaultVal;
        if (StringUtils.hasLength(str)) {
            num = Long.parseLong(str);
        }
        return num;
    }

    /**
     * 获取当前页
     * 
     * @param str
     * @return
     */
    public static Long getPage(String str) {
        return string2Long(str, 1L);
    }

    /**
     * 获取每页行数
     * 
     * @param str
     * @return
     */
    public static Long getRows(String str) {
        return string2Long(str, 10L);
    }

    /**
     * 去除字符串首尾空格
     * 
     * @param str
     * @return
     */
    public static String trim(String str) {
        if (!StringUtils.hasLength(str)) {
            return "";
        }
        return str.trim();
    }

    private static <T> Table getTable(Class<T> clazz) {
        Table table = clazz.getAnnotation(Table.class);
        if (table == null || !StringUtils.hasLength(table.value())) {
            throw new DaoException(
                String.format("Please add the 'Table' annotation on the '%s' Class", clazz.getName()));
        }
        return table;
    }

    /**
     * bytes转对象
     * 
     * @param bytes
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static Object bytes2Object(byte[] bytes) {
        try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream)) {
            return objectInputStream.readObject();
        } catch (Exception e) {
            Map<String, Object> paramMap = new LinkedHashMap<>();
            paramMap.put("bytes", bytes);
            LogUtils.logException(paramMap, e);
            return null;
        }
    }
    
    /**
     * 下划线转驼峰
     * 
     * @param str
     * @return
     */
    public static String underline2Hump(String str) {
        if (!str.contains("_")) {
            return str;
        }
        return underline2HumpMap.computeIfAbsent(str, p -> {
            String temp = p.toLowerCase();
            Matcher matcher = UNDERLINE_PATTERN.matcher(temp);
            StringBuffer sb = new StringBuffer();
            while (matcher.find()) {
                matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
            }
            matcher.appendTail(sb);
            return sb.toString();
        });
    }
    
    /**
     * 下划线转驼峰
     * 
     * @param map
     * @return
     */
    public static Map<String, Object> underline2Hump(Map<String, Object> map) {
        return convertMap(map, ConvertUtils::underline2Hump);
    }
    
    /**
     * 下划线转驼峰
     * 
     * @param list
     * @return
     */
    public static List<? extends Object> underline2Hump(List<? extends Object> list) {
        return convertList(list, ConvertUtils::underline2Hump);
    }

    private static Map<String, Object> convertMap(Map<String, Object> map, Function<String, String> function) {
        Map<String, Object> result = new HashMap<>(16);
        if (ObjectUtils.isEmpty(map)) {
            return result;
        }
        Iterator<Entry<String, Object>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Entry<String, Object> entry = iterator.next();
            String key = entry.getKey();
            Object val = entry.getValue();
            String humpKey = function.apply(key);
            if (ObjectUtils.isEmpty(val)) {
                result.put(humpKey, val);
                continue;
            }
            // val如果是对象或数组，递归处理
            String json = JSON.toJSONString(val);
            JSONValidator.Type jsonType = JSONValidator.from(json).getType();
            switch (jsonType) {
            case Value:
                result.put(humpKey, val);
                break;
            case Object:
                Map<String, Object> childMap = JSON.parseObject(json, new TypeReference<Map<String, Object>>() {
                });
                result.put(humpKey, convertMap(childMap, function));
                break;
            case Array:
                JSONArray array = JSON.parseArray(json);
                List<Object> list = convertList(array, function);
                result.put(humpKey, list);
                break;
            default:
                break;
            }
        }
        return result;
    }
    
    private static List<Object> convertList(List<? extends Object> array, Function<String, String> function) {
        List<Object> list = new ArrayList<>();
        for (Object obj : array) {
            if (ObjectUtils.isEmpty(obj)) {
                list.add(obj);
                continue;
            }
            String json = JSON.toJSONString(obj);
            JSONValidator.Type subJsonType = JSONValidator.from(json).getType();
            switch (subJsonType) {
            case Value:
                list.add(obj);
                break;
            case Object:
                Map<String, Object> map = JSON.parseObject(json,
                        new TypeReference<Map<String, Object>>() {
                        });
                list.add(convertMap(map, function));
                break;
            case Array:
                JSONArray jsonArray = JSON.parseArray(json);
                list.add(convertList(jsonArray, function));
                break;
            default:
                break;
            }
        }
        return list;
    }

    /**
     * 驼峰转下划线
     * 
     * @param str
     * @return
     */
    public static String hump2Underline(String str) {
        return hump2UnderlineMap.computeIfAbsent(str, p -> {
            Matcher matcher = HUMP_PATTERN.matcher(p);
            StringBuffer sb = new StringBuffer();
            while (matcher.find()) {
                matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
            }
            matcher.appendTail(sb);
            return sb.toString();
        });
    }
    
    /**
     * 驼峰转下划线
     * 
     * @param map
     * @return
     */
    public static Map<String, Object> hump2Underline(Map<String, Object> map) {
        return convertMap(map, ConvertUtils::hump2Underline);
    }
    
    /**
     * 驼峰转下划线
     * 
     * @param list
     * @return
     */
    public static List<? extends Object> hump2Underline(List<? extends Object> list) {
        return convertList(list, ConvertUtils::hump2Underline);
    }
    
    /**
     * Blob转换为byte[]
     * @param blob
     * @return
     */
    public static byte[] BlobToBytes(Blob blob) {
        BufferedInputStream bis = null;
        try {
            bis = new BufferedInputStream(blob.getBinaryStream());
            byte[] bytes = new byte[(int) blob.length()];
            int len = bytes.length;
            int offset = 0;
            int read = 0;
            while (offset < len && (read = bis.read(bytes, offset, len - offset)) > 0) {
                offset += read;
            }
            return bytes;
        } catch (SQLException | IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return null;
        } finally {
            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 驱动转dbType
     * 
     * @param driverClassName
     * @return
     */
    public static String getDbType(String driverClassName) {
        String dbType = "";
        switch (driverClassName) {
        case "com.mysql.cj.jdbc.Driver":
        case "org.mariadb.jdbc.Driver":
            dbType = DbTypeEnum.MYSQL.getDbType();
            break;
        case "oracle.jdbc.OracleDriver":
            dbType = DbTypeEnum.ORACLE.getDbType();
            break;
        case "org.postgresql.Driver":
            dbType = DbTypeEnum.POSTGRE_SQL.getDbType();
            break;
        default:
            dbType = DbTypeEnum.MYSQL.getDbType();
            break;
        }
        return dbType;
    }
}
